iT邦幫忙

2023 iThome 鐵人賽

DAY 14
0
Software Development

Womanium Global Quantum Project-Quantum Software&Hardware系列 第 14

Day14->Complete BB84 Protocol Implementation

  • 分享至 

  • xImage
  •  

接下來將實作24量子位元的BB84協議

0. 定義NoisyChannel()和其他有用的函數

# import all necessary objects and methods for quantum circuits
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, execute, Aer
import random
from random import randrange

#Initial source: awards/teach_me_qiskit_2018/cryptography/Cryptography.ipynb

#Code modified to introduce noise in communication channel

def NoisyChannel(qc1, qc2, qc1_name):
    ''' This function takes the output of a circuit qc1 (made up only of x and 
        h gates, simulate noisy quantum channel, where Pauli errors (X - bit flip; Z - phase flip
        will occur in qc2 and then initializes another circuit qc2 with introduce noise.
    ''' 
    
    # Quantum state is retrieved from qasm code of qc1
    qs = qc1.qasm().split(sep=';')[4:-1]

    # Process the code to get the instructions
    for index, instruction in enumerate(qs):
        qs[index] = instruction.lstrip()

    # Parse the instructions and apply to new circuit
    for instruction in qs:
        if instruction[0] == 'x':
            if instruction[5] == '[':
                old_qr = int(instruction[6:-1])
            else:
                old_qr = int(instruction[5:-1])
            qc2.x(qreg[old_qr])
        elif instruction[0] == 'h':
            if instruction[5] == '[':
                old_qr = int(instruction[6:-1])
            else:
                old_qr = int(instruction[5:-1])
            qc2.h(qreg[old_qr])
        elif instruction[0] == 'm': # exclude measuring:
            pass
        else:
            raise Exception('Unable to parse instruction')
    
    ### Introducing noise
    for instruction in qs:
        if randrange(7)<1:
            if instruction[5] == '[':
                old_qr = int(instruction[6:-1])
            else:
                old_qr = int(instruction[5:-1])
            qc2.x(qreg[old_qr]) #apply bit-flip error
        if randrange(7)<1:
            if instruction[5] == '[':
                old_qr = int(instruction[6:-1])
            else:
                old_qr = int(instruction[5:-1])
            qc2.z(qreg[old_qr]) #apply phase-flip error
def print_outcomes_in_reserve(counts): # takes a dictionary variable
    for outcome in counts: # for each key-value in dictionary
        reverse_outcome = ''
        for i in outcome: # each string can be considered as a list of characters
            reverse_outcome = i + reverse_outcome # each new symbol comes before the old symbol(s)
    return reverse_outcome

1. 量子態分佈

建立一個包含 24 個量子位元的電路,Asja 將其發送給 Balvis。

qreg = QuantumRegister(24) # quantum register with 24 qubits
creg = ClassicalRegister(24) # classical register with 24 bits

send=[] #Initial bit string to send
asja_basis=[] #Register to save information about encoding basis
balvis_basis=[] #Register to save information about decoding basis

#Asja
asja = QuantumCircuit(qreg, creg, name='Asja')

for i in range(24):
    bit = randrange(2)
    send.append(bit)
for i, n in enumerate(send):
    if n==1: asja.x(qreg[i]) # apply x-gate
for i in range(24):
    r=randrange(2) #Asja randomly pick a basis
    if r==0: #if bit is 0, then she encodes in Z basis
        asja_basis.append('Z')
    else: #if bit is 1, then she encodes in X basis
        asja.h(qreg[i])
        asja_basis.append('X')

balvis = QuantumCircuit(qreg, creg, name='Balvis') #Defining Balvis circuit
NoisyChannel(asja, balvis, 'Asja') #Asja sends noisy states to Balvis

#Balvis 
for i in range(24):
    r=randrange(2) #Balvis randomly pick a basis
    if r==0: #if bit is 0, then measures in Z basis
        balvis.measure(qreg[i],creg[i])
        balvis_basis.append('Z')
    else: #if bit is 1, then measures in X basis
        balvis.h(qreg[i])
        balvis.measure(qreg[i],creg[i])
        balvis_basis.append('X')

job = execute(balvis,Aer.get_backend('qasm_simulator'),shots=1)
counts = job.result().get_counts(balvis)
counts = print_outcomes_in_reserve(counts)
received = list(map(int, counts))

2. Sifting

#Sifting
asja_key=[] #Asjas register for matching rounds
balvis_key=[] #Balvis register for matching rounds
for j in range(0,len(asja_basis)): #Going through list of bases 
    if asja_basis[j] == balvis_basis[j]: #Comparing
        asja_key.append(send[j])
        balvis_key.append(received[j]) #Keeping key bit if bases matched
    else:
        pass #Discard round if bases mismatched

3. QBER

#QBER
rounds = len(asja_key)//3
errors=0
for i in range(rounds):
    bit_index = randrange(len(asja_key)) 
    tested_bit = asja_key[bit_index]
    if asja_key[bit_index]!=balvis_key[bit_index]: #comparing tested rounds
        errors=errors+1 #calculating errors
    del asja_key[bit_index] #removing tested bits from key strings
    del balvis_key[bit_index]
QBER=errors/rounds #calculating QBER
QBER=round(QBER,2) #saving the answer to two decimal places

print("QBER value =", QBER)
print("Asjas secret key =", asja_key)
print("Balvis secret key =", balvis_key)

輸出結果有可能如下:

QBER value = 0.0
Asjas secret key = [1, 1, 0, 1, 0, 0, 1, 0, 0, 0]
Balvis secret key = [0, 1, 0, 1, 0, 0, 1, 1, 0, 1]

4. Information reconciliation

假設threshold值設定為0.25

  • 如果 QBER =0:Asja 和 Balvis 放棄了 Cascade 協議步驟。
  • 如果 0 < QBER < 0.25:執行級聯協議,並對剩餘密鑰位元進行糾錯。
  • 如果 QBER ≥0.25:Asja 和 Balvis 中止協議並重新啟動。

程式方面則是照著下面的邏輯

1st PASS

  1. Asja 和 Balvis 打亂他們的位元(使得金鑰位元索引仍然匹配)。
  2. 他們將金鑰字串細分為區塊。區塊大小為 𝑤1=0.73/𝑄𝐵𝐸𝑅(四捨五入為整數)。最後一個區塊可以由餘數組成。
  3. 兩者都計算每個區塊的奇偶校驗位並進行比較。具有匹配奇偶校驗的區塊被接受為正確的位元並保存在K_final list中。
  4. 如果奇偶校驗不匹配,他們將使用二分搜尋 - 2nd PASS。

2nd PASS

  1. 他們打亂這些位子。
  2. 將區塊分成兩半併計算奇偶校驗。
  3. 比較奇偶校驗位。具有匹配奇偶校驗的區塊被接受為正確的位元並保存在𝐾_final。
  4. 如果奇偶校驗位不匹配,則它們會套用二分搜尋並重複2nd PASS。
def split(list1, n): 
    out = []
    last = 0.0
    while last < len(list1):
        out.append(list1[int(last):int(last + n)])
        last += n
    return out

def cascade_pass(lA, lB, n): 
#input key lists A-Asja, B-Balvis and target block size to divide in blocks
    #Shuffle
    permutation = list(zip(lA, lB)) #map the index of multiple lists
    random.shuffle(permutation) #performing permutation
    shuffledLA, shuffledLB = zip(*permutation) #unpacking values
    #Split
    splitLA=split(shuffledLA, n)
    splitLB=split(shuffledLB, n)
    #計算奇偶校驗
    #建立空列表,"correctA/B"將包含未發現錯誤的區塊
    #“errorA/B”list包含奇偶校驗不匹配的區塊
    correctA, correctB, errorA, errorB= [], [], [], []
    sumBlocksA = [sum(block) for block in splitLA]
    # calculating parity by first calculating sums of each block in splitA/B
    sumBlocksB = [sum(block) for block in splitLB]
    parityA = [i %2 for i in sumBlocksA]
    #then applying mod(2) operator to our calculated sums and saving results
    parityB = [i %2 for i in sumBlocksB] #in parity bit list
    for i,value in enumerate(range(len(parityA))): #comparing parity bits from list1 with list2
        if parityA[i]==parityB[i]: 
        #if parity bits matched - we add corresponding blocks to our list 'correct'
            correctA.append(splitLA[i])
            correctB.append(splitLB[i])
        else:
            errorA.append(splitLA[i]) 
            #if parity bits mismatched - we add corresponding blocks to our list 'errors'
            errorB.append(splitLB[i])
    keyA = [item for i in correctA for item in i] #Converting our correct blocks into a list
    keyB= [item for i in correctB for item in i]
    return keyA, keyB, errorA, errorB 
    #returning key that consist of correct blocks (list) and blocks with errors (tuple)
#Before starting error correction, we check calculated QBER value
if QBER==0.0:
    print("QBER is 0. Cascade Protocol skipped!")
    print("Final Key Asja", asja_key)
    print("Final Key Balvis", balvis_key)
if QBER>=0.25: 
    print("QBER value is", QBER,"\nThreshold value reached! Protocol Aborted!") #If QBER is above threshold value - we abort protocol
if 0<QBER<=0.25: #if 0<QBER<=0.25 we perform Cascade protocol
    blockSize=0.73//QBER
    kFinalA, kFinalB=[], [] #creating registers for final keys
    #Cascade protocol 1st pass
    corrBlockA, corrBlockB, errBlockA, errBlockB=cascade_pass(asja_key, balvis_key, blockSize) #cascade function
    kFinalA.extend(corrBlockA) #adding block which parity bits matched to final key string
    kFinalB.extend(corrBlockB)
    
#我們現在大概知道初始金鑰字串中有多少個錯誤,
#因為在第一次通過之後,errorA/B list中的每個區塊都包含 1 個(或其他奇數個)錯誤
#我們現在可以在修正這些錯誤之前確定最終(修正後的)金鑰清單長度(當每個區塊中還剩下 1 位元時)
#換句話說,我們的 Cascade 協定的倒數第二遍將有一個金鑰長度

    penultimatePassLength=len(asja_key)-len(errBlockA)
    while len(kFinalA)!=penultimatePassLength: 
    #Bisective search at each block until corrected key length is not equal length of initial key minus error blocks number after first pass
        for i, (blockA, blockB) in enumerate(zip(errBlockA, errBlockB)):
            if len(blockA)>1:
                secondPassA=list(blockA)# we convert block into a lists
                secondPassB=list(blockB)
                blockSize2=len(blockA)//2 
                #we change block size, now we will divide each block that contains an error in halfs
                corrBlockA2, corrBlockB2,  errBlockA2, errBlockB2=cascade_pass(secondPassA, secondPassB, blockSize2) #and apply cascade
                kFinalA.extend(corrBlockA2) #then we will add correct bits to key strings
                kFinalB.extend(corrBlockB2)
                errBlockA[i]=errBlockA2[0] #updating error block values
                errBlockB[i]=errBlockB2[0]
            if len(blockA)==1: 
            #Side case if one block in the round will be shorter than the oner thus will require less passes
                for bit in blockA:
                    if bit==1:
                        bitA=errBlockA[0][0]
                        kFinalA.append(bitA)
                        #Asja adds corresponding bit to her key string without change
                        bitB=errBlockB[0][0]+1 
                        #but Balvis will first correct the error by flipping the bit value 
                        kFinalB.append(bitB)
                    if bit==0:
                        bitA=errBlockA[0][0]
                        kFinalA.append(bitA) 
                        #Asja adds corresponding bit to her key string without change
                        bitB=errBlockB[0][0]-1 
                        #but Balvis will first correct the error by flipping the bit value 
                        kFinalB.append(bitB)
                        
        #print("---PERFORMING NEXT PASS---\n", "Final key Asja:", kFinalA, "\n", "Final key Balvis", kFinalB)
        #print(" Blocks with errors Asja", errBlockA, "\n", "Blocks with errors Balvis", errBlockB)
        
    #After previous passes we have a nested lists, to convert them:    
    errorA=[item for elem in errBlockA for item in elem]
    errorB=[item for elem in errBlockB for item in elem]
    
    #Error correction step, when our error blocks contains just 1 bit (error)
    for i, error in enumerate(zip(errorA, errorB)):
#        bitA=int(''.join(map(str, errorA))) #Converting tuple to integer
#        bitB=int(''.join(map(str, errorB)))
        bitA=int(errorA[i])
        bitB=int(errorB[i])
        if bitA==1:
            kFinalA.append(bitA)
            correctedBitB=bitB+1
            kFinalB.append(correctedBitB)
        if bitA==0:
            kFinalA.append(bitA)
            correctedBitB=bitB-1
            kFinalB.append(correctedBitB)
            
    print("Final Key Asja", kFinalA)
    print("Final Key Balvis", kFinalB)

BICONF strategy

但回想一下奇偶校驗問題!

即使我們看到 QBER =0
或者我們實作了 Cascade 協定並修正了錯誤,這並不意味著我們可以 100% 確定我們的金鑰字串是相同的。一些偷偷摸摸的錯誤位仍然可能隱藏在那裡。

因此,在 QBER=0 的情況下以及實施 Cascade 協議後運行 BICONF 策略非常重要。我們需要確信我們的密鑰是相同的!

如果滿足以下條件,我們將運行 BICONF 策略:

  • QBER =0
  • QBER < 0.25 就在我們完成級聯協議的實作並修正錯誤之後。
    對QBER=0的情況,將使用固定的區塊大小值,每個區塊8位元
    我們會執行8次,在我們得出結論我們的密鑰是正確的之前。
from numpy import log as ln

if QBER!=0: #defining size of blocks
    biconfBlockSize=(4*ln(2))//(3*QBER)
if QBER==0:
    biconfBlockSize=8
kFinalA=asja_key
kFinalB=balvis_key
#print(QBER)

rounds = 0 #counting rounds
biconfError=[] #creating register for rounds with an error
error=0 #register for found and corrected error

while rounds!=8: #we will go through rounds and monitor if blocks with errors will be found 
    rounds=rounds+1
    #Creating random subsets
    kFinalZipped=list(zip(kFinalA, kFinalB)) #maping indexes of our two lists
    randomBlock=random.sample(list(enumerate(kFinalZipped)), int(biconfBlockSize))
    #at this point we will have nested tuple that contains (index of random bit, (bit from Asja string, bit from Balvis string))
    #print(randomBitList) #will print out our nested tuple
    #print(randomBitList[0]) # will print out one block (index, (bitA, bitB))
    #print(randomBitList[0][0]) # will print only first pair index
    #print(randomBitList[0][1][0]) #will print only first pair Asjas' bit
    
    #We will now need to calculate and compare parity bits for both users bits
    sumBlockA=0
    sumBlockB=0
    for i in range(0,int(biconfBlockSize)):
        sumBlockA=sumBlockA+randomBlock[i][1][0]
        sumBlockB=sumBlockB+randomBlock[i][1][1]
    parityA = sumBlockA%2 
#then aplying mod(2) operator to our calculated sums and saving results
    parityB = sumBlockB%2
    
    if parityA!=parityB: 
    #if parities of block dismatch - we bisective search to correct error before continue with next round
        print("Error found in round:", rounds)
        print("Applying bisective search and error correction")
        #Applying bisective search to find and correct an error
        while len(randomBlock)>1: 
#We will take our block with error and run besective search till we find bit with error
            #Split the block
            if len(randomBlock)%2==1: #If block size is odd
                half=len(randomBlock)//2+1 
                #Lenght of our first block should be half+1
            else:
                half=len(randomBlock)//2
            splitBlock=split(randomBlock, half)
            #spliting our block in two parts
            for i, block in enumerate(splitBlock): #For each part
                sumA=0
                sumB=0
                for j in range(0,len(block)): #calculating sums 
                    sumA=sumA+splitBlock[i][j][1][0]
                    sumB=sumB+splitBlock[i][j][1][1]
                parA=sumA%2 #then calculate parities
                parB=sumB%2
                if parA==parB:
                    pass
                if parA!=parB:
 #if parities dismatch- we update our block and run while loop again
                    randomBlock=splitBlock[i]
        if len(randomBlock)==1: #once we isolate the error to 1 bit
            error=error+1
            print("Error found in bit:", randomBlock[0][0]) 
            #we retrieving the index of bit pair
            errorIndex=int(randomBlock[0][0])
            #Apply error correction at Balvis' initial key string
        if kFinalB[errorIndex]==0:
            kFinalB[errorIndex]=1
        else:
            kFinalB[errorIndex]=0
        print("Error corrected!\n")
    else: #If parities matched
        pass

print("BICONF strategy completed!\n", error, "errors found!")
print("Final key Asja", kFinalA)
print("Final key Balvis", kFinalB)

輸出結果也許會是這樣

Error found in round: 2
Applying bisective search and error correction
Error found in bit: 7
Error corrected!

Error found in round: 5
Applying bisective search and error correction
Error found in bit: 0
Error corrected!

Error found in round: 6
Applying bisective search and error correction
Error found in bit: 9
Error corrected!

BICONF strategy completed!
3 errors found!
Final key Asja [1, 1, 0, 1, 0, 0, 1, 0, 0, 0]
Final key Balvis [1, 1, 0, 1, 0, 0, 1, 0, 0, 0]

5. Privacy Amplification

#Privacy amplification
import hashlib

#Generating seed (salt)
seed=[]
for i in kFinalA:
    a=randrange(2)
    seed.append(a)

#Adding seeds to the keys
kFinalA.append(seed)
kFinalB.append(seed)

#Converting lists to strings
strKFinalA = ' '.join([str(elem) for elem in kFinalA])
strKFinalB = ' '.join([str(elem) for elem in kFinalB])

#checking first bit to decide hash function to use
if kFinalA[0]==1:
    resultA=hashlib.sha256(strKFinalA.encode())
    print("Asjas' final key:", bin(int(resultA.hexdigest(), 16))[2:])
else:
    resultA=hashlib.sha3_256(strKFinalA.encode())
    print("Asjas' final key:", bin(int(resultA.hexdigest(), 16))[2:])

print()
if kFinalB[0]==1:
    resultB=hashlib.sha256(strKFinalB.encode())
    print("Balvis' final key:", bin(int(resultB.hexdigest(), 16))[2:])
else:
    resultB=hashlib.sha3_256(strKFinalB.encode())
    print("Balvis' final key:", bin(int(resultB.hexdigest(), 16))[2:])

輸出結果如下:

Asjas' final key: 1100000011001011100101111111011000101011110100011011100011100010111100001001010010000000101011010000100111111000001100111000100010010010111010101110011100110000011010010011010100100010111001000000111000011110011000000110010101001010000101110110111110110001

Balvis' final key: 1100000011001011100101111111011000101011110100011011100011100010111100001001010010000000101011010000100111111000001100111000100010010010111010101110011100110000011010010011010100100010111001000000111000011110011000000110010101001010000101110110111110110001

如此一來就完成完整的BB84協議,並確保了通訊過程加密的安全性!!

參考資料: womanium 教材


上一篇
Day13->Information reconciliation
下一篇
Day15->Man-in-the-middle Attack (MITM)
系列文
Womanium Global Quantum Project-Quantum Software&Hardware30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言